package com.ant.aspect;


import com.ant.annotation.WebLog;
import com.ant.utils.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.security.auth.message.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Web日志切面类
 * 
 * 使用AOP技术自动记录Web请求的日志信息
 * 包括请求URL、入参、出参、执行时间、IP地址等详细信息
 * 通过@WebLog注解标记需要记录日志的方法
 * 
 * @Auther: 灏
 * @Date: 2025/6/1 - 06 - 01 - 17:16
 * @Description: com.ant.infrastructure.aspect
 * @version: 1.0
 */
@Slf4j
@Aspect
@Component
//@Profile({"dev","test"}) //指定打印日志的环境
public class WebLogAspect {
    
    /**
     * 定义切点
     * 
     * 以@WebLog注解为切点，只对标记了该注解的方法进行日志记录
     * 可以通过修改切点表达式来调整日志记录的范围
     * 
     * 示例：@Pointcut("execution(public * *.controller.*.*(..)) || execution(public * com.ant.*.controller.*.*(..))")
     * 匹配 controller包及其子包下的所有类的所有方法
     */
    @Pointcut("@annotation(com.ant.annotation.WebLog)")
    public void webLog() {
    }
    
    /**
     * 环绕通知
     * 
     * 在目标方法执行前后进行日志记录
     * 记录请求开始时间、结束时间、执行耗时等信息
     * 捕获并记录方法执行过程中的异常
     *
     * @param joinPoint 连接点，包含目标方法的信息
     * @return 目标方法的执行结果
     * @throws Throwable 可能抛出的异常
     */
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        // 获取目标方法的签名信息
        Signature signature = joinPoint.getSignature();
        // AOP代理类的类（class）信息
        signature.getDeclaringType();
        
        // 获取当前HTTP请求的上下文信息
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 防止不是http请求的方法，例如：scheduled定时任务
        if ( sra == null) {
            return joinPoint.proceed();
        }
        HttpServletRequest request = sra.getRequest();

        // 获取 @WebLog 注解的描述信息
        String methodDescription = getAspectLogDescription(joinPoint);

        try {
            // 执行目标方法
            Object proceed = joinPoint.proceed();
            
            // 记录详细的请求日志信息
            log.info("\n 请求url:{} \n 请求入参:{}\n 请求出参:{}\n 请求时间：{} \n 响应时间：{} \n 耗时：{}秒\n 描述信息:{} \n Http method:{} \n 调用全路径以及执行方法:{}.{} \n 请求的 IP:{} ",
                    request.getRequestURL().toString(),joinPoint.getArgs(),proceed, DateUtil.format(startTime), DateUtil.format(),(System.currentTimeMillis() - startTime) / 1000d,
                    methodDescription,request.getMethod(),joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
                    request.getRemoteAddr());
            return proceed;
        } catch (Throwable e) {
            // 根据异常类型记录不同的错误日志
            if (e instanceof AuthException) {
                log.info("RESPONSE ERROR:{}",e);
            } else if (e instanceof MethodArgumentNotValidException) {
                log.info("RESPONSE ERROR:{}", e.getMessage());
            } else {
                log.error("RESPONSE ERROR:{}", Arrays.toString(e.getStackTrace()));
            }
            throw e;
        } finally {
            // 记录方法执行的总耗时
            long endTime = System.currentTimeMillis();
            log.info("SPEND TIME : {}ms", (endTime - startTime));
        }
    }

    /**
     * 获取切面注解的描述信息
     * 
     * 通过反射获取方法上@WebLog注解的description属性值
     * 用于在日志中显示方法的业务描述
     *
     * @param joinPoint 切点
     * @return 注解的描述信息
     * @throws Exception 反射异常
     */
    private String getAspectLogDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        StringBuilder description = new StringBuilder("");
        
        // 遍历目标类的所有方法，找到匹配的方法并获取注解信息
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description.append(method.getAnnotation(WebLog.class).description());
                    break;
                }
            }
        }
        return description.toString();
    }
}
